﻿// -----------------------------------------------------------------------
// <copyright file="TupleGenerator.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Generators
{
    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>
    /// 
    /// </summary>
    public static class TupleGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Descriptor BuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper)
        {
            bool isUniversal = true;

            Type genericType = type.GetGenericTypeDefinition();
            Type[] subTypes = type.GetGenericArguments();

            bool isKeyValuePair = genericType == typeof(KeyValuePair<,>);

            var descriptors = new Descriptor[subTypes.Length];
            var properties = new MemberExpression[subTypes.Length];

            Type undecoratedType = Serializer.UndecorateType(type);

            ParameterExpression item = Expression.Parameter(undecoratedType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;

            LambdaExpression disposeExpression;
            LambdaExpression deserializeExpression;
            LambdaExpression serializeExpression;
            LambdaExpression sizeExpression;

            for (int i = 0; i < subTypes.Length; i++)
            {
                descriptors[i] = Serializer.GetSerializer(serializer, subTypes[i], subTypes[i], helper);
                isUniversal &= descriptors[i].IsUniversal;

                if (!isKeyValuePair)
                {
                    if (i != 7)
                    {
                        properties[i] = Expression.Property(item, "Item" + (i + 1));
                    }
                    else
                    {
                        properties[i] = Expression.Property(item, "Rest");
                    }
                }
            }

            if (isKeyValuePair)
            {
                properties[0] = Expression.Property(item, "Key");
                properties[1] = Expression.Property(item, "Value");
            }

            /***/
            {
                var block = new List<Expression>();

                for (int i = 0; i < subTypes.Length; i++)
                {
                    if (descriptors[i].DeserializationHandling.HasFlag(DeserializationHandling.RequireDispose))
                    {
                        block.Add(Serializer.MakeFromLambda(descriptors[i].DisposeExpression, false, properties[i]));
                    }
                }

                disposeExpression = block.Count == 0 ? null : Serializer.MakeLambda(Expression.Block(block), false, item);
            }

            /***/
            {
                Type[] undecoratedSubTypes = undecoratedType.GetGenericArguments();

                var parameters2 = new Expression[subTypes.Length];
                var subItems = new ParameterExpression[subTypes.Length];

                var block = new List<Expression>();

                for (int i = 0; i < subTypes.Length; i++)
                {
                    subItems[i] = Expression.Variable(undecoratedSubTypes[i]);

                    if (!(descriptors[i].DefaultExpression is DefaultExpression))
                    {
                        block.Add(Expression.Assign(subItems[i], descriptors[i].DefaultExpression));
                    }

                    parameters2[i] = Serializer.MakeFromLambda(descriptors[i].DeserializeExpression, descriptors[i].IsUniversal, Serializer.ReaderExpression, subItems[i], parameters);
                }

                ConstructorInfo constructor = undecoratedType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, undecoratedSubTypes, null);

                block.Add(Expression.New(constructor, parameters2));

                BlockExpression expression = Expression.Block(subItems, block);

                deserializeExpression = Serializer.MakeLambda(expression, isUniversal, Serializer.ReaderExpression, item, parameters);
            }

            /***/
            {
                var block = new Expression[subTypes.Length];

                for (int i = 0; i < subTypes.Length; i++)
                {
                    block[i] = Serializer.MakeFromLambda(descriptors[i].SerializeExpression, descriptors[i].IsUniversal, Serializer.WriterExpression, properties[i], parameters);
                }

                serializeExpression = Serializer.MakeLambda(Expression.Block(block), isUniversal, Serializer.WriterExpression, item, parameters);
            }

            /***/
            {
                var block = new Expression[subTypes.Length];

                ParameterExpression size2 = Expression.Variable(typeof(int));

                for (int i = 0; i < subTypes.Length; i++)
                {
                    if (i == 0)
                    {
                        block[i] = Expression.Assign(size2, Serializer.MakeFromLambda(descriptors[i].SizeExpression, descriptors[i].IsUniversal, properties[i], parameters));
                    }
                    else
                    {
                        block[i] = Expression.AddAssign(size2, Serializer.MakeFromLambda(descriptors[i].SizeExpression, descriptors[i].IsUniversal, properties[i], parameters));
                    }
                }

                sizeExpression = Serializer.MakeLambda(Expression.Block(new[] { size2 }, block), isUniversal, item, parameters);
            }

            var descriptor = new Descriptor(type, isUniversal, deserializeExpression, DeserializationHandling.RequireAssignment, serializeExpression, sizeExpression, disposeExpression);
            return descriptor;
        }
    }
}